/**
 ******************************************************************************
 * @file    httpc_req.c
 * @author  Chavis.Chen
 * @version V1.0.0
 * @date    06-Dec-2018
 * @brief   This file defines http request implementation.
 ******************************************************************************
 *
 *  The MIT License
 *  Copyright (c) 2018 Chavis.Chen.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is furnished
 *  to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 *  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 */



#include "httpc_req.h"

char * httpc_request_method_tbl[HTTP_REQ_MTD_MAX] = {HTTP_REQ_MTD_LIST(HTTP_REQ_MTD_TBL_FILL)};
char * httpc_request_head_tbl[HTTP_REQ_HEAD_MAX] = {HTTP_REQ_HEAD_LIST(HTTP_REQ_HEAD_TBL_FILL)};


OSStatus httpc_request_wm_lock(httpc_request_t * httpc_request_ptr)
{
	OSStatus err = kNoErr;

	require_action(httpc_request_ptr, exit, err = kParamErr);

	err = rtos_mutex_lock(httpc_request_ptr->wm_mgr.mutex, QUEC_WAIT_FOREVER);
	require_noerr(err, exit);
	
exit:
	return err;
}

OSStatus httpc_request_wm_unlock(httpc_request_t * httpc_request_ptr)
{
	OSStatus err = kNoErr;

	require_action(httpc_request_ptr, exit, err = kParamErr);

	err = rtos_mutex_unlock(httpc_request_ptr->wm_mgr.mutex);
	require_noerr(err, exit);
	
exit:
	return err;
}

OSStatus httpc_request_body_linklist_lock(httpc_request_t * httpc_request_ptr)
{
	OSStatus err = kNoErr;

	require_action(httpc_request_ptr, exit, err = kParamErr);

	err = rtos_mutex_lock(httpc_request_ptr->body_linklist_mgr.mutex, QUEC_WAIT_FOREVER);
	require_noerr(err, exit);
	
exit:
	return err;
}

OSStatus httpc_request_body_linklist_unlock(httpc_request_t * httpc_request_ptr)
{
	OSStatus err = kNoErr;

	require_action(httpc_request_ptr, exit, err = kParamErr);

	err = rtos_mutex_unlock(httpc_request_ptr->body_linklist_mgr.mutex);
	require_noerr(err, exit);
	
exit:
	return err;
}

OSStatus httpc_request_new
(
	httpc_request_t * httpc_request_ptr,
	uint32 wm_size,
	uint32 wm_low_val,
	uint32 wm_high_val
)
{
	OSStatus err = kNoErr;
	
	void (* cbLow)(void *);
	void (* cbHigh)(void *);
	void (* cbEmpty)(void *);

	require_action(httpc_request_ptr, exit, err = kParamErr);

	cbLow = httpc_request_ptr->wm_mgr.cb.reached_low;
	cbHigh = httpc_request_ptr->wm_mgr.cb.reached_high;
	cbEmpty = httpc_request_ptr->wm_mgr.cb.wm_empty;

	memset(httpc_request_ptr, 0, sizeof(httpc_request_t));
	
	err = linklist_init(&httpc_request_ptr->head_linklist_mgr.handle);
	require_noerr(err, exit);

	err = linklist_init(&httpc_request_ptr->body_linklist_mgr.handle);
	require_noerr(err, exit);

	err = wm_init(&httpc_request_ptr->wm_mgr.handle, wm_size, wm_low_val, wm_high_val, cbLow, cbHigh, cbEmpty);
	require_noerr(err, exit);

	err = rtos_mutex_create(&httpc_request_ptr->wm_mgr.mutex);
	require_noerr(err, exit);

exit:
	if(err != kNoErr && httpc_request_ptr != NULL)
	{
		if(httpc_request_ptr->head_linklist_mgr.handle != NULL)
		{
			linklist_deinit(&httpc_request_ptr->head_linklist_mgr.handle);
		}
		if(httpc_request_ptr->body_linklist_mgr.handle != NULL)
		{
			linklist_deinit(&httpc_request_ptr->body_linklist_mgr.handle);
		}
		if(httpc_request_ptr->wm_mgr.handle != NULL)
		{
			wm_deinit(&httpc_request_ptr->wm_mgr.handle);
		}
		if(httpc_request_ptr->wm_mgr.mutex != NULL)
		{
			rtos_mutex_delete(httpc_request_ptr->wm_mgr.mutex);
		}

		memset(httpc_request_ptr, 0, sizeof(httpc_request_t));
	}
	return err;
}

OSStatus httpc_request_add_line(httpc_request_t * httpc_request_ptr, httpc_request_method_e httpc_request_method, char * path)
{
	OSStatus 	err = kNoErr;
	node_cnt_t		node_cnt = 0;
	char *			request_line = NULL;
	uint16			request_line_len = 0;

	require_action(httpc_request_ptr && httpc_request_method < HTTP_REQ_MTD_MAX && path, 
				   exit, err = kParamErr);

	err = linklist_get_node_cnt(&httpc_request_ptr->head_linklist_mgr.handle, &node_cnt);
	require_noerr(err, exit);

	require_action(node_cnt == 0, exit, err = kOperationErr);

	request_line_len = strlen(httpc_request_method_tbl[httpc_request_method]) + 1 + strlen(path) + 1 + 10;	/*> <MTD> <URL> HTTP/1.1<CR><LF> */
	
	request_line = malloc(request_line_len + 1);
	require_action(request_line, exit, err = kNoMemoryErr);

	memset(request_line, 0, request_line_len + 1);

	sprintf(request_line, "%s %s %s\r\n", httpc_request_method_tbl[httpc_request_method], path, HTTP_PROT_VER);
	
	err = linklist_push(&httpc_request_ptr->head_linklist_mgr.handle, DIR_FIFO, request_line, strlen(request_line));
	require_noerr(err, exit);

	httpc_request_ptr->head_linklist_mgr.total_len += (request_line_len + 3);		/*> will add "\r\n\0" at the end of head */

exit:
	if(request_line)
		free(request_line);
	
	return err;
}

OSStatus httpc_request_add_head(httpc_request_t * httpc_request_ptr, char * request_head_name, char * request_head_value)
{
	OSStatus 	err = kNoErr;
	node_cnt_t		node_cnt = 0;
	char *			request_head = NULL;
	uint16			request_head_len = 0;
	linklist_node_t *		linklist_node = NULL;
	char *			request_head_tmp = NULL;
	int				i = 0;
	
	require_action(httpc_request_ptr && request_head_name && request_head_value, exit, err = kParamErr);

	err = linklist_get_node_cnt(&httpc_request_ptr->head_linklist_mgr.handle, &node_cnt);
	require_noerr(err, exit);

	require_action(node_cnt != 0, exit, err = kOperationErr);

	if(node_cnt > 1)
	{
		for(i=1; i<node_cnt; i++)
		{
			err = linklist_node_find(&httpc_request_ptr->head_linklist_mgr.handle, i, &linklist_node);
			require_noerr(err, exit);

			request_head_tmp = malloc(linklist_node->size + 1);
			require_action(request_head_tmp, exit, kNoMemoryErr);

			memset(request_head_tmp, 0, linklist_node->size + 1);
			
			memcpy(request_head_tmp, linklist_node->data, linklist_node->size);

			if(strncasecmp(request_head_tmp, request_head_name, strlen(request_head_name)) == 0)
			{
				free(request_head_tmp);
				require_noerr(err = kAlreadyInUseErr, exit);
			}

			free(request_head_tmp);
		}
	}

	request_head_len = strlen(request_head_name) + 2 + strlen(request_head_value) + 2;			/*> <name>: <value><CR><LF> */

	request_head = malloc(request_head_len + 1);
	require_action(request_head, exit, err = kNoMemoryErr);

	memset(request_head, 0, request_head_len + 1);

	sprintf(request_head, "%s: %s\r\n", request_head_name, request_head_value);

	err = linklist_push(&httpc_request_ptr->head_linklist_mgr.handle, DIR_FIFO, request_head, strlen(request_head));
	require_noerr(err, exit);

	httpc_request_ptr->head_linklist_mgr.total_len += request_head_len;
	
exit:
	if(request_head)
		free(request_head);
	
	return err;
}

OSStatus httpc_request_rmv_head(httpc_request_t * httpc_request_ptr, char * request_head_name)
{
	OSStatus 	err = kNoErr;
	node_cnt_t		node_cnt = 0;
	linklist_node_t *		linklist_node = NULL;
	char *			request_head_tmp = NULL;
	int				i = 0;

	require_action(httpc_request_ptr && request_head_name, exit, err = kParamErr);

	err = linklist_get_node_cnt(&httpc_request_ptr->head_linklist_mgr.handle, &node_cnt);
	require_noerr(err, exit);

	require_action(node_cnt > 1, exit, err = kOperationErr);

	for(i=1; i<node_cnt; i++)
	{
		err = linklist_node_find(&httpc_request_ptr->head_linklist_mgr.handle, i, &linklist_node);
		require_noerr(err, exit);

		request_head_tmp = malloc(linklist_node->size + 1);
		require_action(request_head_tmp, exit, kNoMemoryErr);

		memset(request_head_tmp, 0, linklist_node->size + 1);
		
		memcpy(request_head_tmp, linklist_node->data, linklist_node->size);

		if(strncasecmp(request_head_tmp, request_head_name, strlen(request_head_name)) == 0)
		{
			free(request_head_tmp);

			err = linklist_node_delete(&httpc_request_ptr->head_linklist_mgr.handle, i);
			require_noerr(err, exit);
		}

		free(request_head_tmp);
	}

exit:
	return err;
}

OSStatus httpc_request_generate_pkt_head(httpc_request_t * httpc_request_ptr)
{
	OSStatus 	err = kNoErr;
	node_cnt_t		node_cnt = 0;
	data_t *		node_data = NULL;
	datasize_t		node_data_len = 0;
	char *			request_head_out_buffer = NULL;
	int 			i = 0;

	require_action(httpc_request_ptr, exit, err = kParamErr);

	err = linklist_get_node_cnt(&httpc_request_ptr->head_linklist_mgr.handle, &node_cnt);
	require_noerr(err, exit);

	require_action(node_cnt != 0, exit, err = kOperationErr);

	request_head_out_buffer = malloc(httpc_request_ptr->head_linklist_mgr.total_len);

	memset(request_head_out_buffer, 0, httpc_request_ptr->head_linklist_mgr.total_len);
	
	for(; i<node_cnt; i++)
	{
		err = linklist_pop(&httpc_request_ptr->head_linklist_mgr.handle, &node_data, &node_data_len);
		require_noerr(err, exit);

		memcpy(request_head_out_buffer + strlen(request_head_out_buffer), node_data, node_data_len);
		
		linklist_mem_free(node_data);
	}

	memcpy(request_head_out_buffer + strlen(request_head_out_buffer), "\r\n\0", 3);

	err = wm_write(&httpc_request_ptr->wm_mgr.handle, request_head_out_buffer, strlen(request_head_out_buffer), NULL, httpc_request_ptr->wm_mgr.handle);
	require_noerr(err, exit);
	
exit:
	if(request_head_out_buffer)
	{
		free(request_head_out_buffer);
	}
	return err;
}

OSStatus httpc_request_destroy(httpc_request_t * httpc_request_ptr)
{
	OSStatus 	err = kNoErr;

	require_action(httpc_request_ptr, exit, err = kParamErr);

	if(httpc_request_ptr->head_linklist_mgr.handle != NULL)
	{
		linklist_deinit(&httpc_request_ptr->head_linklist_mgr.handle);
	}

	if(httpc_request_ptr->body_linklist_mgr.handle != NULL)
	{
		linklist_deinit(&httpc_request_ptr->body_linklist_mgr.handle);
	}

	if(httpc_request_ptr->wm_mgr.handle != NULL)
	{
		wm_deinit(&httpc_request_ptr->wm_mgr.handle);
	}

	if(httpc_request_ptr->wm_mgr.mutex != NULL)
	{
		rtos_mutex_delete(httpc_request_ptr->wm_mgr.mutex);
	}

	memset(httpc_request_ptr, 0, sizeof(httpc_request_t));
	
exit:
	return err;
}
